﻿// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Roslynator.CSharp.Refactorings.InlineDefinition;

internal class InlineMethodAnalyzer : InlineAnalyzer<InvocationExpressionSyntax, MethodDeclarationSyntax, IMethodSymbol>
{
    public static InlineMethodAnalyzer Instance { get; } = new();

    protected override bool ValidateNode(InvocationExpressionSyntax node, TextSpan span)
    {
        ExpressionSyntax expression = node.Expression;

        if (expression is null)
            return false;

        ArgumentListSyntax argumentList = node.ArgumentList;

        if (argumentList is null)
            return false;

        if (expression.Kind() == SyntaxKind.SimpleMemberAccessExpression)
        {
            return ((MemberAccessExpressionSyntax)expression).Name?.Span.Contains(span) == true;
        }
        else
        {
            return expression.Span.Contains(span);
        }
    }

    protected override IMethodSymbol GetMemberSymbol(
        InvocationExpressionSyntax node,
        SemanticModel semanticModel,
        CancellationToken cancellationToken)
    {
        if (semanticModel.GetSymbol(node, cancellationToken) is not IMethodSymbol methodSymbol)
            return null;

        if (methodSymbol.Language != LanguageNames.CSharp)
            return null;

        MethodKind methodKind = methodSymbol.MethodKind;

        if (methodKind == MethodKind.Ordinary)
        {
            if (methodSymbol.IsStatic)
                return methodSymbol;

            INamedTypeSymbol enclosingType = semanticModel.GetEnclosingNamedType(node.SpanStart, cancellationToken);

            if (SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, enclosingType))
            {
                ExpressionSyntax expression = node.Expression;

                if (!expression.IsKind(SyntaxKind.MemberBindingExpression))
                {
                    if (!expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)
                        || ((MemberAccessExpressionSyntax)expression).Expression.IsKind(SyntaxKind.ThisExpression))
                    {
                        return methodSymbol;
                    }
                }
            }
        }
        else if (methodKind == MethodKind.ReducedExtension
            && node.Expression?.Kind() == SyntaxKind.SimpleMemberAccessExpression)
        {
            return methodSymbol;
        }

        return null;
    }

    protected override async Task<MethodDeclarationSyntax> GetMemberDeclarationAsync(
        IMethodSymbol symbol,
        CancellationToken cancellationToken)
    {
        foreach (SyntaxReference reference in symbol.DeclaringSyntaxReferences)
        {
            SyntaxNode node = await reference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);

            if ((node is MethodDeclarationSyntax methodDeclaration)
                && methodDeclaration.BodyOrExpressionBody() is not null)
            {
                return methodDeclaration;
            }
        }

        return null;
    }

    protected override ImmutableArray<ParameterInfo> GetParameterInfos(
        InvocationExpressionSyntax node,
        IMethodSymbol symbol)
    {
        bool isReduced = symbol.MethodKind == MethodKind.ReducedExtension;

        ImmutableArray<IParameterSymbol> parameters = symbol.Parameters;

        List<ParameterInfo> parameterInfos = null;

        SeparatedSyntaxList<ArgumentSyntax> arguments = node.ArgumentList.Arguments;

        foreach (ArgumentSyntax argument in arguments)
        {
            IParameterSymbol parameterSymbol = DetermineParameterHelper.DetermineParameter(argument, arguments, parameters);

            if (parameterSymbol is not null)
            {
                var parameterInfo = new ParameterInfo(parameterSymbol, argument.Expression);

                (parameterInfos ??= new List<ParameterInfo>()).Add(parameterInfo);
            }
            else
            {
                return default;
            }
        }

        foreach (IParameterSymbol parameterSymbol in parameters)
        {
            if (parameterInfos is null
                || parameterInfos.FindIndex(f => SymbolEqualityComparer.Default.Equals(f.ParameterSymbol, parameterSymbol)) == -1)
            {
                if (parameterSymbol.HasExplicitDefaultValue)
                {
                    var parameterInfo = new ParameterInfo(parameterSymbol, null);

                    (parameterInfos ??= new List<ParameterInfo>()).Add(parameterInfo);
                }
                else
                {
                    return default;
                }
            }
        }

        if (isReduced)
        {
            var memberAccess = (MemberAccessExpressionSyntax)node.Expression;

            ExpressionSyntax expression = memberAccess.Expression;

            SyntaxNode nodeIncludingConditionalAccess = node.WalkUp(f => f.IsKind(SyntaxKind.ConditionalAccessExpression));

            if (nodeIncludingConditionalAccess != node)
            {
                int startIndex = expression.Span.End - nodeIncludingConditionalAccess.SpanStart;
                expression = SyntaxFactory.ParseExpression(nodeIncludingConditionalAccess.ToString().Remove(startIndex));
            }

            var parameterInfo = new ParameterInfo(symbol.ReducedFrom.Parameters[0], expression.TrimTrivia(), isThis: true);

            (parameterInfos ??= new List<ParameterInfo>()).Add(parameterInfo);
        }

        return (parameterInfos is not null)
            ? parameterInfos.ToImmutableArray()
            : ImmutableArray<ParameterInfo>.Empty;
    }

    protected override (ExpressionSyntax expression, SyntaxList<StatementSyntax> statements) GetExpressionOrStatements(MethodDeclarationSyntax declaration)
    {
        BlockSyntax body = declaration.Body;

        if (body is null)
            return (declaration.ExpressionBody?.Expression, default(SyntaxList<StatementSyntax>));

        SyntaxList<StatementSyntax> statements = body.Statements;

        if (!statements.Any())
            return (null, default(SyntaxList<StatementSyntax>));

        switch (statements.SingleOrDefault(shouldThrow: false))
        {
            case ReturnStatementSyntax returnStatement:
                return (returnStatement.Expression, default(SyntaxList<StatementSyntax>));
            case ExpressionStatementSyntax expressionStatement:
                return (expressionStatement.Expression, default(SyntaxList<StatementSyntax>));
        }

        if (!declaration.ReturnsVoid())
            return (null, default(SyntaxList<StatementSyntax>));

        return (null, statements);
    }

    protected override InlineRefactoring<InvocationExpressionSyntax, MethodDeclarationSyntax, IMethodSymbol> CreateRefactoring(
        Document document,
        SyntaxNode node,
        INamedTypeSymbol nodeEnclosingType,
        IMethodSymbol symbol,
        MethodDeclarationSyntax declaration,
        ImmutableArray<ParameterInfo> parameterInfos,
        SemanticModel nodeSemanticModel,
        SemanticModel declarationSemanticModel)
    {
        return new InlineMethodRefactoring(document, node, nodeEnclosingType, symbol, declaration, parameterInfos, nodeSemanticModel, declarationSemanticModel);
    }

    protected override RefactoringDescriptor GetDescriptor()
    {
        return RefactoringDescriptors.InlineMethod;
    }
}
